<?php

namespace CMS\Model;

use Think\Model;
use Think\Upload;

/**
 * 文件模型
 * 负责文件的下载和上传
 */
class File extends Model
{
    protected $name = 'cms_file';
    protected $tableName = 'file';

    /**
     * 文件模型自动完成
     * 
     * @var array
     */
    protected $_auto = array(
        array(
            'create_time',
            NOW_TIME,
            self::MODEL_INSERT
        )
    );

    /**
     * 文件模型字段映射
     * 
     * @var array
     */
    protected $_map = array(
        'type' => 'mime'
    );

    /**
     * 文件上传
     * 
     * @param array $files
     *            要上传的文件列表（通常是$_FILES数组）
     * @param array $setting
     *            文件上传配置
     * @param string $driver
     *            上传驱动名称
     * @param array $config
     *            上传驱动配置
     * @return array 文件上传成功后的信息
     */
    public function upload($files, $setting, $driver = 'Local', $config = null)
    {
        /* 上传文件 */
        $setting['callback'] = array(
            $this,
            'isFile'
        );
        $setting['removeTrash'] = array(
            $this,
            'removeTrash'
        );
        $Upload = new Upload($setting, $driver, $config);
        $info = $Upload->upload($files);
        
        /* 设置文件保存位置 */
        $this->_auto[] = array(
            'location',
            'ftp' === strtolower($driver) ? 1 : 0,
            self::MODEL_INSERT
        );
        
        if ($info) { // 文件上传成功，记录文件信息
            foreach ($info as $key => &$value) {
                /* 已经存在文件记录 */
                if (isset($value['id']) && is_numeric($value['id'])) {
                    $value['path'] = substr($setting['rootPath'], 1) . $value['savepath'] . $value['savename']; // 在模板里的url路径
                    continue;
                }
                
                $value['path'] = substr($setting['rootPath'], 1) . $value['savepath'] . $value['savename']; // 在模板里的url路径
                /* 记录文件信息 */
                if ($this->create($value) && ($id = $this->add())) {
                    $value['id'] = $id;
                } else {
                    // TODO: 文件上传成功，但是记录文件信息失败，需记录日志
                    unset($info[$key]);
                }
            }
            return $info; // 文件上传成功
        } else {
            $this->error = $Upload->getError();
            return false;
        }
    }

    /**
     * 下载指定文件
     * 
     * @param number $root
     *            文件存储根目录
     * @param integer $id
     *            文件ID
     * @param string $args
     *            回调函数参数
     * @return boolean false-下载失败，否则输出下载文件
     */
    public function download($root, $id, $callback = null, $args = null)
    {
        /* 获取下载文件信息 */
        $file = $this->find($id);
        if (! $file) {
            $this->error = '不存在该文件！';
            return false;
        }
        
        /* 下载文件 */
        switch ($file['location']) {
            case 0: // 下载本地文件
                $file['rootpath'] = $root;
                return $this->downLocalFile($file, $callback, $args);
            case 1: // 下载FTP文件
                $file['rootpath'] = $root;
                return $this->downFtpFile($file, $callback, $args);
                break;
            default:
                $this->error = '不支持的文件存储类型！';
                return false;
        }
    }

    /**
     * 检测当前上传的文件是否已经存在
     * 
     * @param array $file
     *            文件上传数组
     * @return boolean 文件信息， false - 不存在该文件
     */
    public function isFile($file)
    {
        if (empty($file['md5'])) {
            throw new \Exception('缺少参数:md5');
        }
        /* 查找文件 */
        $map = array(
            'md5' => $file['md5'],
            'sha1' => $file['sha1']
        );
        return $this->field(true)
            ->where($map)
            ->find();
    }

    /**
     * 下载本地文件
     * 
     * @param array $file
     *            文件信息数组
     * @param callable $callback
     *            下载回调函数，一般用于增加下载次数
     * @param string $args
     *            回调函数参数
     * @return boolean 下载失败返回false
     */
    private function downLocalFile($file, $callback = null, $args = null)
    {
        if (is_file($file['rootpath'] . $file['savepath'] . $file['savename'])) {
            /* 调用回调函数新增下载数 */
            is_callable($callback) && call_user_func($callback, $args);
            
            /* 执行下载 */
            // TODO: 大文件断点续传
            header("Content-Description: File Transfer");
            header('Content-type: ' . $file['type']);
            header('Content-Length:' . $file['size']);
            if (preg_match('/MSIE/', $_SERVER['HTTP_USER_AGENT'])) { // for IE
                header('Content-Disposition: attachment; filename="' . rawurlencode($file['name']) . '"');
            } else {
                header('Content-Disposition: attachment; filename="' . $file['name'] . '"');
            }
            readfile($file['rootpath'] . $file['savepath'] . $file['savename']);
            exit();
        } else {
            $this->error = '文件已被删除！';
            return false;
        }
    }

    /**
     * 下载ftp文件
     * 
     * @param array $file
     *            文件信息数组
     * @param callable $callback
     *            下载回调函数，一般用于增加下载次数
     * @param string $args
     *            回调函数参数
     * @return boolean 下载失败返回false
     */
    private function downFtpFile($file, $callback = null, $args = null)
    {
        /* 调用回调函数新增下载数 */
        is_callable($callback) && call_user_func($callback, $args);
        
        $host = C('DOWNLOAD_HOST.host');
        $root = explode('/', $file['rootpath']);
        $file['savepath'] = $root[3] . '/' . $file['savepath'];
        
        $data = array(
            $file['savepath'],
            $file['savename'],
            $file['name'],
            $file['mime']
        );
        $data = json_encode($data);
        $key = think_encrypt($data, C('DATA_AUTH_KEY'), 600);
        
        header("Location:http://{$host}/onethink.php?key={$key}");
    }

    /**
     * 清除数据库存在但本地不存在的数据
     * 
     * @param
     *            $data
     */
    public function removeTrash($data)
    {
        $this->where(array(
            'id' => $data['id']
        ))->delete();
    }
}
